#include <cerrno>
#include <climits>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fcntl.h>
#include <libconfig.h>
#include <linux/limits.h>

#include "config.h"

#if __linux
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#endif

// ok
configuration_t *create_config() {
  configuration_t *new_config =
      (configuration_t *)calloc(1, sizeof(configuration_t));

  return new_config;
}

// ok
config_t get_config(configuration_t *config) { return config->config; }

// ok
static void create_user_local_config_file(const char *file_path) {
  int fd = open(file_path, O_WRONLY | O_CLOEXEC | O_CREAT | O_EXCL,
                S_IRUSR | S_IWUSR);

  if (0 > fd) {
    fprintf(stderr, "Failed to open default configuration file in %s (%s)\n",
            file_path, strerror(errno));
    exit(EXIT_FAILURE);
  }

  const char *buf = CONFIG_CONTENT;
  size_t written = 0u;
  ssize_t ret = 0u;

  do {
    ret = write(fd, (void *)(buf + written), (size_t)(strlen(buf) - written));

    if (-1 == ret) {
      fprintf(stderr,
              "Failed to write to default configuration file in %s (%s)\n",
              file_path, strerror(errno));
      exit(EXIT_FAILURE);
    }

    written += ret;
  } while (strlen(buf) > written);

  close(fd);
}

// ok
void load_config(configuration_t *config) {
  config_init(&config->config);

  char user_config_rc_file[PATH_MAX];
#ifdef __linux
  char *xdg_config_home = getenv("XDG_CONFIG_HOME");

  if (xdg_config_home != NULL) {
    snprintf(user_config_rc_file, PATH_MAX, "%s/%s/%s", xdg_config_home,
             CONFIG_DIR, CONFIG_FILE);
    if (config_read_file(&config->config, user_config_rc_file))
      return;
  }

  char *xdg_config_dirs = getenv("XDG_CONFIG_DIRS");

  if (xdg_config_dirs != NULL) {
    char xdg_config_dirs_cpy[PATH_MAX * 5];
    strncpy(xdg_config_dirs_cpy, xdg_config_dirs, strlen(xdg_config_dirs) - 1);

    char *token = strtok(xdg_config_dirs_cpy, ":");

    while (token != NULL) {
      char sys_config_rc_file[PATH_MAX];
      snprintf(sys_config_rc_file, PATH_MAX, "%s/%s/%s", token, CONFIG_DIR,
               CONFIG_FILE);
      if (config_read_file(&config->config, sys_config_rc_file))
        return;
      token = strtok(NULL, ":");
    }
  }
#elif __APPLE__
  char *home = getenv("HOME");

  if (home != NULL) {
    snprintf(user_config_rc_file, PATH_MAX, "%s/Library/Preferences/%s/%s",
             home, CONFIG_DIR, CONFIG_FILE);

    if (config_read_file(&config->config, user_config_rc_file))
      return;
  }
#endif

  char dir[PATH_MAX - strlen(CONFIG_FILE)];

#ifdef __linux
  if (!xdg_config_home) {
#endif
    snprintf(dir, sizeof(dir), "%s/.config/%s", getenv("HOME"), CONFIG_DIR);
#ifdef __linux
  } else {
    snprintf(dir, sizeof(dir), "%s/%s", xdg_config_home, CONFIG_DIR);
  }
#endif

  if (mkdir(dir, 0777) < 0 && errno != EEXIST) {
    fprintf(stderr, "Failed to create configuration directory %s (%s)\n", dir,
            strerror(errno));
    exit(EXIT_FAILURE);
  }

  snprintf(user_config_rc_file, sizeof(dir), "%s/%s", dir, CONFIG_FILE);

  if (access(user_config_rc_file, R_OK) < 0)
    create_user_local_config_file(user_config_rc_file);

  if (config_read_file(&config->config, user_config_rc_file))
    return;

  fprintf(stderr, "Failed to create default configuration file in %s\n",
          user_config_rc_file);
  exit(EXIT_FAILURE);
}

// ok
void destroy_config(configuration_t *config) { free(config); }